package com.zhoug.common3.encrypt;

import android.util.Base64;

import com.zhoug.common3.Logger;
import com.zhoug.common3.utils.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import androidx.annotation.NonNull;


/**
 * RSA非对称加密,使用{@link Builder}创建
 * 线程安全,每次加解密都会创建新的Cipher实例
 *
 * @Author 35574
 * @Date 2021/7/19
 * @Description
 */
public class RSAHelper implements IEncryptor {
    private static final String TAG = ">>>RSAHelper";
    private static boolean debug = true;

    /**
     * RSA算法
     */
    public static final String RSA = "RSA";
    /**加密方式，android的*/
    /**
     * 加密方式，标准jdk的
     */
    public String transformation;
    /**
     * 字符串编码方式
     */
    private Charset charset;
    /**
     * DEFAULT :最后有个换行符\n
     * NO_PADDING 这个参数是略去加密字符串最后的”=”
     * NO_WRAP 这个参数意思是略去所有的换行符
     */
    private int base64Flag;

    /**
     * 默认公钥 base64编码格式
     */
    private String publicKey;
    /**
     * 默认私钥 base64编码格式
     */
    private String privateKey;
    /**
     * 公钥
     */
    private PublicKey mPublicKey;
    /**
     * 私钥
     */
    private PrivateKey mPrivateKey;

    private Base64Utils _mBase64Utils;
    /**
     * 1024位的证书，加密时最大支持117个字节，解密时为128
     * 2048位的证书，加密时最大支持245个字节，解密时为256。
     * 加密时支持的最大字节数：证书位数/8 -11（比如：2048位的证书，支持的最大加密字节数：2048/8 - 11 = 245）,[只要用到padding，那么就要占用实际的明文长度,PKCS1Padding就占用了11个字节]
     * 解密时支持的最大字节数：证书位数/8（比如：2048位的证书，支持的最大解密字节数：2048/8  = 256）
     * <p>
     * 加解密最长128位,超过则分段加解密
     */
    private int ENCRYPT_MAX_BLOCK_LENGTH = 0;//证书位数/8 -11
    private int DECRYPT_MAX_BLOCK_LENGTH = 0;//证书位数/8
    private static final int RESERVE_BYTES=11;//加密block需要预留字节数

    public static final class Builder {
        private Charset charset = StandardCharsets.UTF_8;
        private int base64Flag = Base64.NO_WRAP;
        private String transformation = EncryptConstants.TRANSFORMATION_RSA_NONE_P1;
        private String publicKey;
        private String privateKey;


        public Builder() {
        }

        /**
         * 设置字符串编码方式 默认:StandardCharsets.UTF_8
         *
         * @param charset
         * @return
         */
        public Builder setCharset(@NonNull Charset charset) {
            this.charset = charset;
            return this;
        }

        /**
         * 设置base64 flag 默认:Base64.NO_WRAP
         *
         * @param base64Flag
         * @return
         */
        public Builder setBase64Flag(int base64Flag) {
            this.base64Flag = base64Flag;
            return this;
        }

        /**
         * 设置 算法/模式/填充 默认 "AES/ECB/PKCS7Padding"
         *
         * @param transformation
         * @return
         */
        public Builder setTransformation(@NonNull String transformation) {
            this.transformation = transformation;
            return this;
        }

        /**
         * 设置公钥
         *
         * @param publicKey
         * @return
         */
        public Builder setPublicKey(String publicKey) {
            this.publicKey = publicKey;
            return this;
        }

        /**
         * 设置私钥
         *
         * @param privateKey
         * @return
         */
        public Builder setPrivateKey(String privateKey) {
            this.privateKey = privateKey;
            return this;
        }

        public RSAHelper build() {
            RSAHelper rsaHelper = new RSAHelper();
            //base64配置
            rsaHelper.base64Flag = base64Flag;
            rsaHelper.charset = charset;
            rsaHelper.transformation = transformation;
            //公钥私钥
            rsaHelper.privateKey = privateKey;
            rsaHelper.publicKey = publicKey;

            return rsaHelper;
        }
    }


    private RSAHelper() {

    }

    /**
     * 获取公钥
     *
     * @return
     */
    private PublicKey getPublicKey() {
        if (mPublicKey == null) {
            synchronized (this) {
                if (mPublicKey == null) {
                    byte[] decode = getBase64Utils().decode(publicKey);
                    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decode);
                    try {
                        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
                        mPublicKey = keyFactory.generatePublic(keySpec);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return mPublicKey;
    }

    /**
     * 获取私钥
     *
     * @return
     */
    private PrivateKey getPrivateKey() {
        if (mPrivateKey == null) {
            synchronized (this) {
                if (mPrivateKey == null) {
                    try {
                        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(getBase64Utils().decode(privateKey));
                        KeyFactory kf = KeyFactory.getInstance(RSA);
                        mPrivateKey = kf.generatePrivate(keySpec);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            }
        }
        return mPrivateKey;
    }

    /**
     * 创建加密器
     *
     * @return
     */
    private Cipher createEncryptCipher() {
        Cipher cp = null;
        try {
            cp = Cipher.getInstance(transformation);
            cp.init(Cipher.ENCRYPT_MODE, getPublicKey());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cp;
    }

    /**
     * 创建解密器
     *
     * @return
     */
    private Cipher createDecryptCipher() {
        Cipher cp = null;
        try {
            cp = Cipher.getInstance(transformation);
            cp.init(Cipher.DECRYPT_MODE, getPrivateKey());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cp;
    }

    /**
     * 获取 Base64Utils
     *
     * @return
     */
    public Base64Utils getBase64Utils() {
        if (_mBase64Utils == null) {
            synchronized (this) {
                if (_mBase64Utils == null) {
                    _mBase64Utils = new Base64Utils(charset, base64Flag);
                }
            }
        }
        return _mBase64Utils;
    }


    /**
     * 加密
     *
     * @param data 源数据
     * @return 加密后的字符数组
     */
    public byte[] encrypt(byte[] data) {
       /* if (data == null) {
            return null;
        }
        try {
            if (ENCRYPT_MAX_BLOCK_LENGTH == 0) {
                ENCRYPT_MAX_BLOCK_LENGTH = calculateEncryptBlockLength(publicKey);
            }
            if (debug) {
                Logger.d(TAG, "encrypt:ENCRYPT_MAX_DECRYPT_BLOCK=" + ENCRYPT_MAX_BLOCK_LENGTH);
            }
            int inputLen = data.length;
            if (inputLen < ENCRYPT_MAX_BLOCK_LENGTH) {
                return createEncryptCipher().doFinal(data);
            } else {
                //分段加密
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] cache;
                int i = 0;
                int offSet = 0;
                while (inputLen - offSet > 0) {
                    if (inputLen - offSet > ENCRYPT_MAX_BLOCK_LENGTH) {
                        cache = createEncryptCipher().doFinal(data, offSet, ENCRYPT_MAX_BLOCK_LENGTH);
                    } else {
                        cache = createEncryptCipher().doFinal(data, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    i++;
                    offSet = i * ENCRYPT_MAX_BLOCK_LENGTH;
                }
                byte[] decryptedData = out.toByteArray();
                out.close();
                return decryptedData;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;*/
        if (data == null) {
            return null;
        }
        return encrypt(data, 0, data.length);
    }

    @Override
    public byte[] encrypt(byte[] data, int inputOffset, int inputLen) {
        if (data == null) {
            return null;
        }
        try {
            if (ENCRYPT_MAX_BLOCK_LENGTH == 0) {
                ENCRYPT_MAX_BLOCK_LENGTH = calculateEncryptBlockLength(publicKey);
            }
            if (debug) {
//                Logger.d(TAG, "encrypt:ENCRYPT_MAX_DECRYPT_BLOCK=" + ENCRYPT_MAX_BLOCK_LENGTH);
            }
            if (inputLen <= ENCRYPT_MAX_BLOCK_LENGTH) {
                return createEncryptCipher().doFinal(data, inputOffset, inputLen);
            } else {
                //分段加密
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] cache;
                int offSet = inputOffset;
                while (inputLen - offSet > 0) {
                    if (inputLen - offSet > ENCRYPT_MAX_BLOCK_LENGTH) {
                        cache = createEncryptCipher().doFinal(data, offSet, ENCRYPT_MAX_BLOCK_LENGTH);
                    } else {
                        cache = createEncryptCipher().doFinal(data, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    offSet += ENCRYPT_MAX_BLOCK_LENGTH;
                }
                byte[] decryptedData = out.toByteArray();
                out.close();
                return decryptedData;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }


    @Override
    public byte[] encrypt(String data) {
        if (data == null) {
            return null;
        }
        return encrypt(data.getBytes(charset));
    }

    /**
     * 加密为base64字符串
     *
     * @param data 源数据
     * @return base64字符串
     */
    public String encryptToBase64(byte[] data) {
        return getBase64Utils().encodeToString(encrypt(data));
    }

    /**
     * 加密为base64字符串
     *
     * @param data 源数据
     * @return base64字符串
     */
    public String encryptToBase64(String data) {
        if (data == null) {
            return null;
        }
        return getBase64Utils().encodeToString(encrypt(data.getBytes(charset)));
    }

    /**
     * 解密
     *
     * @param data 加密后的字符数组
     * @return 源数据
     */
    public byte[] decrypt(byte[] data) {
        /*if (data == null) {
            return null;
        }
        try {
            if (DECRYPT_MAX_BLOCK_LENGTH == 0) {
                DECRYPT_MAX_BLOCK_LENGTH = calculateDecryptBlockLength(publicKey);
            }
            if (debug) {
                Logger.d(TAG, "encrypt:DECRYPT_MAX_DECRYPT_BLOCK=" + DECRYPT_MAX_BLOCK_LENGTH);
            }
            int inputLen = data.length;
            if (inputLen < DECRYPT_MAX_BLOCK_LENGTH) {
                return createDecryptCipher().doFinal(data);
            } else {
                //分段解密
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] cache;
                int i = 0;
                int offSet = 0;
                Cipher decryptCipher = createDecryptCipher();
                while (inputLen - offSet > 0) {
                    if (inputLen - offSet > DECRYPT_MAX_BLOCK_LENGTH) {
                        cache = decryptCipher.doFinal(data, offSet, DECRYPT_MAX_BLOCK_LENGTH);
                    } else {
                        cache = decryptCipher.doFinal(data, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    i++;
                    offSet = i * DECRYPT_MAX_BLOCK_LENGTH;
                }
                byte[] decryptedData = out.toByteArray();
                out.close();
                return decryptedData;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;*/
        if (data == null) {
            return null;
        }
        return decrypt(data, 0, data.length);
    }


    @Override
    public byte[] decrypt(byte[] data, int inputOffset, int inputLen) {
        if (data == null) {
            return null;
        }
        try {
            if (DECRYPT_MAX_BLOCK_LENGTH == 0) {
                DECRYPT_MAX_BLOCK_LENGTH = calculateDecryptBlockLength(publicKey);
            }
            if (debug) {
//                Logger.d(TAG, "encrypt:DECRYPT_MAX_DECRYPT_BLOCK=" + DECRYPT_MAX_BLOCK_LENGTH);
            }
            if (inputLen <= DECRYPT_MAX_BLOCK_LENGTH) {
                return createDecryptCipher().doFinal(data, inputOffset, inputLen);
            } else {
                //分段解密
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] cache;
                int offSet = inputOffset;
                Cipher decryptCipher = createDecryptCipher();
                while (inputLen - offSet > 0) {
                    if (inputLen - offSet > DECRYPT_MAX_BLOCK_LENGTH) {
                        cache = decryptCipher.doFinal(data, offSet, DECRYPT_MAX_BLOCK_LENGTH);
                    } else {
                        cache = decryptCipher.doFinal(data, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    offSet += DECRYPT_MAX_BLOCK_LENGTH;
                }
                byte[] decryptedData = out.toByteArray();
                out.close();
                return decryptedData;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    @Override
    public byte[] decrypt(String data) {
        if (data == null) {
            return null;
        }
        return decrypt(getBase64Utils().decode(data));
    }

    /**
     * 解密
     *
     * @param data 加密后的数据
     * @return 源数据
     */
    public String decryptToString(byte[] data) {
        byte[] decrypt = decrypt(data);
        if (decrypt != null) {
            return new String(decrypt, 0, decrypt.length, charset);
        }
        return null;
    }

    /**
     * 解密
     *
     * @param data 加密后的Base64字符串
     * @return 源数据
     */
    public String decryptToString(String data) {
        byte[] decrypt = decrypt(getBase64Utils().decode(data));
        if (decrypt != null) {
            return new String(decrypt, 0, decrypt.length, charset);
        }
        return null;
    }

    /**
     * 加密数据流
     * @param in  待加密的输入数据流
     * @param out 加密后的输出数据流
     */
    @Override
    public void encrypt(InputStream in, OutputStream out) {
        try {
            //创建加密流
            Cipher encryptCipher = createEncryptCipher();
            if (ENCRYPT_MAX_BLOCK_LENGTH == 0) {
                ENCRYPT_MAX_BLOCK_LENGTH = calculateEncryptBlockLength(publicKey);
            }
            byte[] buf=new byte[ENCRYPT_MAX_BLOCK_LENGTH];
            int len;
            while ((len=in.read(buf))>0){
                byte[] bytes = encryptCipher.doFinal(buf, 0, len);
                if(bytes!=null && bytes.length>0){
                    out.write(bytes,0,bytes.length);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            IOUtils.close(out);
            IOUtils.close(in);

        }
    }

    /**
     * 解密数据流
     *
     * @param in  待解密的输入数据流
     * @param out 解密后的输出数据流
     */
    @Override
    public void decrypt(InputStream in, OutputStream out) {
        try {
            //创建解密流
            Cipher decryptCipher = createDecryptCipher();
            if (DECRYPT_MAX_BLOCK_LENGTH == 0) {
                DECRYPT_MAX_BLOCK_LENGTH = calculateDecryptBlockLength(publicKey);
            }
            byte[] buf=new byte[DECRYPT_MAX_BLOCK_LENGTH];
            int len;
            while ((len=in.read(buf))>0){
                byte[] bytes = decryptCipher.doFinal(buf, 0, len);
                if(bytes!=null && bytes.length>0){
                    out.write(bytes,0,bytes.length);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            IOUtils.close(out);
            IOUtils.close(in);

        }
    }

    /**
     * 根据公钥计算加密数据的最大长度
     *
     * @param key base64编码后的key
     */
    public static int calculateEncryptBlockLength(String key) {
        byte[] decode = Base64.decode(key, Base64.NO_WRAP);
        int size = decode.length * 8 / 512 * 512;
        Logger.d(TAG, "key length=" + size);
        int blockLength = size / 8 - RESERVE_BYTES;
        Logger.d(TAG, "blockLength=" + blockLength);
        return blockLength;
    }

    /**
     * 根据公钥计算加密数据的最大长度
     *
     * @param key
     */
    public static int calculateEncryptBlockLength(byte[] key) {
        int size = key.length * 8 / 512 * 512;
        Logger.d(TAG, "key length=" + size);
        int blockLength = size / 8 - RESERVE_BYTES;
        Logger.d(TAG, "blockLength=" + blockLength);
        return blockLength;
    }


    /**
     * 根据公钥计算解密数据的最大长度
     *
     * @param key base64编码后的key
     */
    public static int calculateDecryptBlockLength(String key) {
        byte[] decode = Base64.decode(key, Base64.NO_WRAP);
        int size = decode.length * 8 / 512 * 512;
        Logger.d(TAG, "key length=" + size);
        int blockLength = size / 8;
        Logger.d(TAG, "blockLength=" + blockLength);
        return blockLength;
    }

    /**
     * 根据公钥计算解密数据的最大长度
     *
     * @param key
     */
    public static int calculateDecryptBlockLength(byte[] key) {
        int size = key.length * 8 / 512 * 512;
        Logger.d(TAG, "key length=" + size);
        int blockLength = size / 8;
        Logger.d(TAG, "blockLength=" + blockLength);
        return blockLength;
    }

    /**
     * 计算加密后的秘文长度
     *
     * @param byteSize
     * @param key      公钥
     * @return
     */
    public static int calculateSecretLength(int byteSize, String key) {
        int blockLength = RSAHelper.calculateDecryptBlockLength(key);//128
        int max = RSAHelper.calculateEncryptBlockLength(key) + 1;//118
        return (byteSize / max + 1) * blockLength;
    }

    /**
     * 计算加密后的秘文长度
     *
     * @param byteSize
     * @return
     */
    public int calculateSecretLength(int byteSize) {
        int blockLength = RSAHelper.calculateDecryptBlockLength(publicKey);//128
        int max = RSAHelper.calculateEncryptBlockLength(publicKey) + 1;//118
        return (byteSize / max + 1) * blockLength;
    }

    public void getOutputLength(){
        PublicKey publicKey = getPublicKey();
        byte[] encoded = publicKey.getEncoded();
        if(encoded!=null){
            Logger.d(TAG, "getOutputLength:"+encoded.length+","+encoded.length*8);
        }

    }

}
